<?php

class Com_Basil_DynamicData_Object {

	private static $entityDefinition = array();

	/**
	 * @var Array
	 */
	protected $definition;

	protected $objectProperties = array();

	protected $unsavedData = null;

	protected $objectKey = null;

	protected $entity;

	protected $hbaseclient;
	
	private $objectTableName = 'object-repo';

	private $internalPropertyNames = array('seqVersion','entity');

	public $seqVersion = 0;

	public static function registerDefinition($json) {
		$jsonDef = Zend_Json::decode($json);
		self::$entityDefinition[$jsonDef['name']] = $jsonDef;
		$name = $jsonDef['name'];		
	}
	
	private static function varToPhpCode($obj) {
		if (!is_array($obj)) {
			return '"'.str_replace('"', '\"', $obj) . '"';
		}
		$ret = 'array( ';
		$first = true;
		foreach($obj as $key => $value) {
			if (!$first) {
				$ret .= ',';	
			} else {
				$first = false;
			}
			$value = self::varToPhpCode($value);
			$ret .= '"'.str_replace('"', '\"', $key) . '" => '. $value;
		}
		$ret .= ')';
		return $ret;
	}
	
	public static function normalizeName($str) {
		$str = str_replace('-', ' ', $str);
		$name = ucwords($str);
		$name = str_replace(' ','', $name);
		return $name;
		
	}
	
	public static function generateClassFromJson($json) {
		$jsonObject = Zend_Json::decode($json);
		$constants = Zend_Registry::get('constants');
		$classLocation = $constants->AUTOGEN_CLASS_LOCATION;
		if ($classLocation == null || $classLocation == '') {
			throw new Com_Basil_DynamicData_Exception('Auto generated class location is not defined in constants file');
		}
		
		$classname = $jsonObject['name'];
		$entityName = $classname;
		$classname = self::normalizeName($classname);
		
		
		$fieldsArray = self::varToPhpCode($jsonObject['fields']);
		
		$phpClass = new Zend_CodeGenerator_Php_Class();
		$phpClass->setName('BASIL_'.$classname);
		if (!isset($jsonObject['inherits'])) {
			$phpClass->setExtendedClass('Com_Basil_DynamicData_Object');
		} else {
			$phpClass->setExtendedClass('BASIL_'.self::normalizeName($jsonObject['inherits']));
		}
		
		$constructor = new Zend_CodeGenerator_Php_Method();
		$constructor->setName('__construct');
		$constructor->setBody("parent::__construct('$entityName');\n\$this->addEntityFieldsList($fieldsArray);");
		$phpClass->setMethod($constructor);
		
		$file = new Zend_CodeGenerator_Php_File();
		$file->setClass($phpClass);

		file_put_contents($classLocation. "/BASIL/$classname.php", $file->generate());
		
	}

	/**
	 * @param $entity
	 * @return unknown_type
	 */
	public function __construct($entity) {
		$this->entity = $entity;

		$front = Zend_Controller_Front::getInstance();
		$this->hbaseclient = $front->getParam('bootstrap')->getResource('hbaseClient');
	}

	/**
	 * @return unknown_type
	 */
	public function getEntity() {
		return $this->entity;
	}
	
	public function addEntityFieldsList($fieldsArray) {
		if (!isset($this->definition['fields'])) {
			$this->definition['fields'] = array();
		}
		$this->definition['fields'] = array_merge($this->definition['fields'],$fieldsArray);
	}


	/**
	 * @param $name
	 * @param $value
	 * @return unknown_type
	 */
	public function setField($name, $value) {
		if (!isset($this->definition['fields'][$name])) {
			throw new Com_Basil_DynamicData_Exception("Field [$name] does not exist.");
		}

		$this->validateField($name, $value);

		if ($this->unsavedData == null && $this->objectProperties != null) {
			//copy from obj prop (for update)
			$this->unsavedData = $this->objectProperties;
		}

		if (isset($this->definition['fields'][$name]['type'])) {
			$fieldType = $this->definition['fields'][$name]['type'];
			if ($fieldType == 'entity') {
				$value = $value->getKey();
			} else if ($fieldType == 'collection') {
				return $this->addToCollection($name, $value);				
			}
		}

		$this->unsavedData[$name] = $value;
		return true;
	}
	
	/**
	 * Enter description here ...
	 * @param unknown_type $name
	 * @param unknown_type $value
	 */
	public function addToCollection($name, $value) {
			
	}

	/**
	 * Get object property
	 * @param $name
	 * @return unknown_type
	 */
	public function getField($name) {
		$value = '';

		if (!isset($this->definition['fields'][$name])) {
			throw new Com_Basil_DynamicData_Exception("Field [$name] does not exist.");
		}
		if ($this->unsavedData != null) {
			$value = $this->unsavedData[$name];
		} else {
			$value = $this->objectProperties[$name];
		}

		if (isset($this->definition['fields'][$name]['type'])) {
			if ($this->definition['fields'][$name]['type'] == 'entity' && (!is_a($value,'Com_Basil_DynamicData_Object'))) {
				//$object = new Com_Basil_DynamicData_Object($this->definition['fields'][$name]['entity']);
				$className = 'BASIL_' . self::normalizeName($this->definition['fields'][$name]['entity']);
				$reflectionClass = new ReflectionClass($className);
				$object = $reflectionClass->newInstance(array());
				$object->load($value);
				$value = $object;
				if ($this->unsavedData != null) {
					$this->unsavedData[$name] = $value; //assign object back to the array
				} else {
					$this->objectProperties[$name] = $value;
				}
			} else if ($this->definition['fields'][$name]['type'] == 'collection' && (!is_a($value,'Com_Basil_DynamicData_Collection'))) {
				
			}
		}

		return $value;
	}

	public function getKey() {
		if ($this->objectKey == null) {
			throw new Com_Basil_DynamicData_Exception('Key is not available');
		}
		return $this->objectKey;
	}

	/**
	 * Generate Key if not exists
	 * @return Base-36 string
	 */
	private function generateKey() {
		if ($this->objectKey == null) {
			$key = $this->generateRandomKey();
			$this->objectKey = $key;
		}
		return $this->objectKey;
	}
	
	public static function generateRandomKey() {
		$random1 = mt_rand(60466176,2147483647);
		$random2 = mt_rand(60466176,2147483647);
		$key = uniqid($random1.'-'.$random2,true);
		$key = md5($key);
		return $key;
	}

	/**
	 * Retrieve data from HBase
	 * @param $key object key
	 * @return unknown_type
	 */
	public function load($key) {
		$row = $this->hbaseclient->getRow($this->objectTableName, $key);
		if ($row == null) {
			throw new Com_Basil_DynamicData_Exception("Unable to find key [$key]");
		}
		$this->read($row);
	}


	/**
	 * Read ONE row data from HBase Data model
	 * @param $row
	 * @return unknown_type
	 */
	public function read($row) {
		$this->objectKey = $row->key;
		$this->unsavedData = null;
		$entityValidated = false;
		foreach($row->columns as $col) {
			$cs = explode(':',$col->columnName);
			//$this->setField($cs[1], $col->data);
			if ($cs[0]=='custom') {
				$this->objectProperties[$cs[1]] = $col->data;
			} else {
				switch($cs[1]) {
					case 'seqVersion':
						$this->seqVersion = $col->data;
						break;
					case 'entity':
						$entityName = $col->data;
						if ($entityName != $this->entity) {
							throw new Com_Basil_DynamicData_ValidationException("Invalid entity retrieved");
						}
						$entityValidated = true;
						break;
					default:
				}
			}
		}
		if (!$entityValidated) {
			throw new Com_Basil_DynamicData_ValidationException("Entity name is missing from the data.");
		}
		if ($this->seqVersion ==0) {
			//something is missing...
			throw new Com_Basil_DynamicData_Exception('Missing version');
		}
	}

	/**
	 * @return unknown_type
	 */
	public function create() {
		//validate all fields again
		$columns = array();
		$this->seqVersion = 1;
		foreach($this->definition['fields'] as $fieldName => $fieldDef) {
			//$this->validateField($fieldName, $this->unsavedData[$fieldName]);
			if (!isset($this->unsavedData[$fieldName])) {
				continue;
			}
			$columns[] = new Com_Basil_HBase_Data_Column("custom:$fieldName", $this->unsavedData[$fieldName]);
		}
		foreach($this->internalPropertyNames as $propName) {
			$columns[] = new Com_Basil_HBase_Data_Column('internal:'.$propName, $this->$propName);
		}
		$row = new Com_Basil_HBase_Data_Row($this->generateKey(), $columns);

		$this->hbaseclient->insertUpdateRow($this->objectTableName, array($row));

		$this->updateIndex();

		return $row->key;
	}


	/**
	 * @return unknown_type
	 */
	public function update() {
		//validate all fields again
		$columns = array();

		//optimistic lock
		$checkOption = new Com_Basil_HBase_Data_Column('internal:seqVersion', $this->seqVersion);
		$this->seqVersion++;

		foreach($this->definition['fields'] as $fieldName => $fieldDef) {
			//$this->validateField($fieldName, $this->unsavedData[$fieldName]);
			$columns[] = new Com_Basil_HBase_Data_Column("custom:$fieldName", $this->unsavedData[$fieldName]);
		}
		foreach($this->internalPropertyNames as $propName) {
			$columns[] = new Com_Basil_HBase_Data_Column('internal:'.$propName, $this->$propName);
		}

		$row = new Com_Basil_HBase_Data_Row($this->generateKey(), $columns, $checkOption);

		$ret = $this->hbaseclient->insertUpdateRow($this->objectTableName, array($row));
		if ($ret == 0) {
			// did not get optimistic lock
			throw new Com_Basil_DynamicData_OptimisticLockException('Unable to obtain optimistic lock');
		}

		$this->updateIndex();

		return $row->key;
	}
	
	/**
	 * Enter description here ...
	 */
	public function searchForRowKeys($criteria) {
		
	}
	

	/**
	 * Enter description here ...
	 */
	private function updateIndex() {

		if (!isset($this->definition['indices'])) {
			return; //no index to update
		}

		//what was changed?
		$changedFields = array();
		foreach($this->definition['fields'] as $fieldName => $fieldDef) {
			if ($this->objectProperties == null || (!isset($this->objectProperties[$fieldName]))) {
				$changedFields[] = $fieldName;
			} else if (isset($this->unsavedData[$fieldName]) && isset($this->objectProperties[$fieldName]) &&
			$this->unsavedData[$fieldName] !=  $this->objectProperties[$fieldName] ) {
				$changedFields[] = $fieldName;
			}
		}

		foreach($this->definition['indices'] as $indexName => $fields) {
			//create index, [entity name]\t[index name]\t[fields..]\t[unique id]
			$indexRowKey = $this->entity."\t".$indexName;
			foreach($fields as $field) {
				$value = $this->getField($field);
				$value = str_replace("\t","#tab#",$value);
				$indexRowKey .= "\t$value";
			}
			$indexRowKey .= "\t".$this->generateRandomKey();
			$columns = array(
				new Com_Basil_HBase_Data_Column('idx:to', $this->getKey())
			);
			$indexRow = new Com_Basil_HBase_Data_Row($indexRowKey, $columns);	
			$this->hbaseclient->insertUpdateRow('index_table',array($indexRow));		
		}
	}


	/**
	 * @param $name
	 * @param $value
	 * @return unknown_type
	 */
	private function validateField($name, $value) {
		//validate data
		if (isset($this->definition['fields'][$name]['validation'])) {
			$validationMethod =  'validation_'.$this->definition['fields'][$name]['validation'];
			if (!method_exists($this,$validationMethod)) {
				throw new Com_Basil_DynamicData_Exception("Validation method $validationMethod is not available");
			}
			if (!$this->$validationMethod($name,$value)) {
				throw new Com_Basil_DynamicData_ValidationException("Invalid data value for [$name]");
			}
		}
	}

	/**
	 * @param $name
	 * @param $value
	 * @return unknown_type
	 */
	private function validation_regex($name,$value) {
		$pattern = $this->definition['fields'][$name]['regex'];
		if (!preg_match("/$pattern/",$value)) {
			return false;
		}
		return true;
	}

	private function validation_entity($name, $value) {
		if(!is_a($value,'Com_Basil_DynamicData_Object')) {
			return false;
		}
		if ($value->getKey() == null) {
			return false;
		}
		return true;
	}
}
?>